/*******************************************************************************
* Copyright (c) 2016 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.docker.ui.testutils.swt;
import static org.assertj.core.api.Assertions.fail;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.linuxtools.internal.docker.ui.views.DockerExplorerView;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.waits.Conditions;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.finders.ContextMenuHelper;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTableItem;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.progress.UIJob;
import org.junit.Assert;
import org.junit.ComparisonFailure;
/**
* Utility class for SWT
*/
public class SWTUtils {
/**
* Calls <strong>synchronously</strong> the given {@link Supplier} in the
* default Display and returns the result
*
* @param supplier
* the Supplier to call
* @return the supplier's result
*/
public static <V> V syncExec(final Supplier<V> supplier) {
final Queue<V> result = new ArrayBlockingQueue<>(1);
Display.getDefault().syncExec(() -> result.add(supplier.get()));
return result.poll();
}
/**
* Executes <strong>synchronously</strong> the given {@link Runnable} in the
* default Display
*
* @param runnable
* the {@link Runnable} to execute
*/
public static void syncExec(final Runnable runnable) {
Display.getDefault().syncExec(runnable);
}
/**
* Executes <strong>synchronously</strong> the given {@link Runnable} in the
* default Display. The given {@link Runnable} is ran into a rapping
* {@link Runnable} that will catch the {@link ComparisonFailure} that may
* be raised during an assertion.
*
* @param runnable
* the {@link Runnable} to execute
* @throws ComparisonFailure
* if an assertion failed.
* @throws SWTException
* if an {@link SWTException} occurred
*/
public static void syncAssert(final Runnable runnable) throws SWTException, ComparisonFailure {
final Queue<ComparisonFailure> failure = new ArrayBlockingQueue<>(1);
final Queue<SWTException> swtException = new ArrayBlockingQueue<>(1);
Display.getDefault().syncExec(() -> {
try {
runnable.run();
} catch (ComparisonFailure e1) {
failure.add(e1);
} catch (SWTException e2) {
swtException.add(e2);
}
});
if (!failure.isEmpty()) {
throw failure.poll();
}
if (!swtException.isEmpty()) {
throw swtException.poll();
}
}
/**
* Executes the given {@link Runnable} <strong>asynchronously</strong> in
* the default {@link Display} and waits until all jobs are done before
* completing.
*
* @param runnable
* @throws InterruptedException
*/
public static void asyncExec(final Runnable runnable) {
asyncExec(runnable, true);
}
/**
* Executes the given {@link Runnable} <strong>asynchronously</strong> in
* the default {@link Display} and waits until all jobs are done before
* completing.
*
* @param runnable
* the {@link Runnable} to execute
* @param waitForJobsToComplete
* boolean flag to indicate if the method should wait for all
* jobs to complete before finishing
* @throws InterruptedException
*/
public static void asyncExec(final Runnable runnable, final boolean waitForJobsToComplete) {
final Queue<ComparisonFailure> failure = new ArrayBlockingQueue<>(1);
final Queue<SWTException> swtException = new ArrayBlockingQueue<>(1);
Display.getDefault().asyncExec(() -> {
try {
runnable.run();
} catch (ComparisonFailure e1) {
failure.add(e1);
} catch (SWTException e2) {
swtException.add(e2);
}
});
if (waitForJobsToComplete) {
waitForJobsToComplete();
}
if (!failure.isEmpty()) {
throw failure.poll();
}
if (!swtException.isEmpty()) {
throw swtException.poll();
}
}
/**
* Waits for all {@link Job} to complete.
*
* @throws InterruptedException
*/
public static void waitForJobsToComplete() {
wait(1, TimeUnit.SECONDS);
while (!Job.getJobManager().isIdle()) {
wait(1, TimeUnit.SECONDS);
}
}
/**
* Waits for all {@link Job} to complete.
*
* @throws InterruptedException
*/
public static void waitForJobsToComplete(Object familly) {
// while (Job.getJobManager().find(familly).length > 0) {
// wait(1, TimeUnit.SECONDS);
// }
Conditions.waitForJobs(DockerExplorerView.class, "Docker Explorer View jobs");
}
/**
* @param viewBot
* the {@link SWTBotView} containing the {@link Tree} to traverse
* @param paths
* the node path in the {@link SWTBotTree} associated with the
* given {@link SWTBotView}
* @return the first {@link SWTBotTreeItem} matching the given node names
*/
public static SWTBotTreeItem getTreeItem(final SWTBotView viewBot, final String... paths) {
final SWTBotTree tree = viewBot.bot().tree();
return getTreeItem(tree.getAllItems(), paths);
}
/**
*
* @param parentTreeItem
* the parent tree item from which to start
* @param paths
* the relative path to the item to return
* @return the {@link SWTBotTreeItem} that matches the given path from the
* given parent tree item
*/
public static SWTBotTreeItem getTreeItem(final SWTBotTreeItem parentTreeItem, final String... paths) {
if (paths.length == 1) {
return getTreeItem(parentTreeItem, paths[0]);
}
final String[] remainingPaths = new String[paths.length - 1];
System.arraycopy(paths, 1, remainingPaths, 0, paths.length - 1);
return getTreeItem(getTreeItem(parentTreeItem, paths[0]), remainingPaths);
}
/**
* Returns the first child node in the given parent tree item whose text
* matches (ie, begins with) the given path argument.
*
* @param parentTree
* the parent tree item
* @param path
* the text of the node that should match
* @return the first matching node or <code>null</code> if none could be
* found
*/
public static SWTBotTreeItem getTreeItem(final SWTBotTree parentTree, final String path) {
for (SWTBotTreeItem child : parentTree.getAllItems()) {
if (child.getText().startsWith(path)) {
return child;
}
}
return null;
}
/**
* Returns the first child node in the given parent tree item whose text
* matches (ie, begins with) the given path argument.
*
* @param parentTreeItem
* the parent tree item
* @param path
* the text of the node that should match
* @return the first matching node or <code>null</code> if none could be
* found
*/
public static SWTBotTreeItem getTreeItem(final SWTBotTreeItem parentTreeItem, final String path) {
for (SWTBotTreeItem child : parentTreeItem.getItems()) {
if (child.getText().startsWith(path)) {
return child;
}
}
return null;
}
private static SWTBotTreeItem getTreeItem(final SWTBotTreeItem[] treeItems, final String[] paths) {
final SWTBotTreeItem swtBotTreeItem = Stream.of(treeItems).filter(item -> item.getText().startsWith(paths[0]))
.findFirst().orElseThrow(() -> new RuntimeException("Only available items: "
+ Stream.of(treeItems).map(item -> item.getText()).collect(Collectors.joining(", "))));
if (paths.length > 1) {
syncExec(() -> swtBotTreeItem.expand());
final String[] remainingPath = new String[paths.length - 1];
System.arraycopy(paths, 1, remainingPath, 0, remainingPath.length);
return getTreeItem(swtBotTreeItem.getItems(), remainingPath);
}
return swtBotTreeItem;
}
public static SWTBotTableItem getListItem(final SWTBotTable table, final String name) {
return Stream.iterate(0, i -> i + 1).limit(table.rowCount()).map(rowNumber -> table.getTableItem(rowNumber))
.filter(rowItem -> {
return Stream.iterate(0, j -> j + 1).limit(table.columnCount())
.map(colNum -> rowItem.getText(colNum)).anyMatch(colValue -> colValue.contains(name));
}).findFirst().orElse(null);
}
/**
* Waits for the given duration
*
* @param duration
* the duration
* @param unit
* the duration unit
*/
public static void wait(final int duration, final TimeUnit unit) {
try {
Thread.sleep(unit.toMillis(duration));
} catch (InterruptedException e) {
fail("Failed to wait for a " + unit.toMillis(duration) + "ms", e);
}
}
/**
* Selects <strong> all child items</strong> in the given
* <code>parentTreeItem</code> whose labels match the given
* <code>items</code>.
*
* @param parentTreeItem
* the parent tree item
* @param matchItems
* the items to select
* @return
*/
public static SWTBotTreeItem select(final SWTBotTreeItem parentTreeItem, final String... matchItems) {
final List<String> fullyQualifiedItems = Stream.of(parentTreeItem.getItems()).filter(
treeItem -> Stream.of(matchItems).anyMatch(matchItem -> treeItem.getText().startsWith(matchItem)))
.map(item -> item.getText()).collect(Collectors.toList());
return parentTreeItem.select(fullyQualifiedItems.toArray(new String[0]));
}
/**
* Selects <strong> all child items</strong> in the given
* <code>parentTreeItem</code> whose labels match the given
* <code>items</code>.
*
* @param parentTree
* the parent tree
* @param matchItems
* the items to select
* @return
*/
public static SWTBotTree select(final SWTBotTree parentTree, final String... matchItems) {
final List<String> fullyQualifiedItems = Stream.of(parentTree.getAllItems()).filter(
treeItem -> Stream.of(matchItems).anyMatch(matchItem -> treeItem.getText().startsWith(matchItem)))
.map(item -> item.getText()).collect(Collectors.toList());
return parentTree.select(fullyQualifiedItems.toArray(new String[0]));
}
/**
* Selects the given <code>treeItem</code> whose labels match the given
* <code>items</code>.
*
* @param treeItem
* the parent tree item
* @param matchItems
* the items to select
*/
public static void select(SWTBotTreeItem treeItem) {
treeItem.select();
}
/**
* @param tree
* the root {@link SWTBotTree}
* @param path
* the path for the menu
* @return the child {@link SWTBotMenu} named with the first item in the
* given <code>path</code> from the given {@link SWTBotTree}
*/
public static SWTBotMenu getContextMenu(final SWTBotTree tree, String... path) {
final SWTBotMenu contextMenu = tree.contextMenu(path[0]);
if (contextMenu == null) {
Assert.fail("Failed to find context menu '" + path[0] + "'.");
}
if (path.length == 1) {
return contextMenu;
}
final String[] remainingPath = new String[path.length - 1];
System.arraycopy(path, 1, remainingPath, 0, remainingPath.length);
return getSubMenu(contextMenu, remainingPath);
}
/**
* Hides the menu for the given <code>tree</code>
*
* @param tree
* the tree whose {@link Menu} should be hidden
*/
public static void hideMenu(final SWTBotTree tree) {
try {
final Menu menu = UIThreadRunnable.syncExec((Result<Menu>) () -> tree.widget.getMenu());
UIThreadRunnable.syncExec(new VoidResult() {
@Override
public void run() {
hide(menu);
}
private void hide(final Menu menu) {
menu.notifyListeners(SWT.Hide, new Event());
if (menu.getParentMenu() != null) {
hide(menu.getParentMenu());
}
}
});
} catch (WidgetNotFoundException e) {
// ignore if widget is not found, that's probably because there's no
// tree in the
// Docker Explorer view for the test that just ran.
}
}
/**
* @param menu
* the parent menu
* @param path
* the path for the menu
* @return the child {@link SWTBotMenu} named with the first item in the
* given <code>path</code> from the given {@link SWTBotMenu}
*/
public static SWTBotMenu getSubMenu(final SWTBotMenu menu, String... path) {
final SWTBotMenu subMenu = menu.menu(path[0]);
if (subMenu == null) {
Assert.fail("Failed to find submenu '" + path[0] + "'.");
}
if (path.length == 1) {
return subMenu;
}
final String[] remainingPath = new String[path.length - 1];
System.arraycopy(path, 1, remainingPath, 0, remainingPath.length);
return getSubMenu(subMenu, remainingPath);
}
public static SWTBotTreeItem expand(final SWTBotTree tree, final String... paths) {
final SWTBotTreeItem rootItem = getTreeItem(tree, paths[0]);
expandTreeItem(rootItem);
if (paths.length > 1) {
final String[] remainingPath = new String[paths.length - 1];
System.arraycopy(paths, 1, remainingPath, 0, remainingPath.length);
return expand(rootItem, remainingPath);
}
return rootItem;
}
public static SWTBotTreeItem expand(final SWTBotTreeItem treeItem, final String... paths) {
final SWTBotTreeItem childItem = getTreeItem(treeItem, paths[0]);
expandTreeItem(childItem);
if (paths.length > 1) {
final String[] remainingPath = new String[paths.length - 1];
System.arraycopy(paths, 1, remainingPath, 0, remainingPath.length);
return expand(childItem, remainingPath);
}
return getTreeItem(treeItem, paths[0]);
}
private static SWTBotTreeItem expandTreeItem(final SWTBotTreeItem treeItem) {
final UIJob expandJob = new UIJob("expanding tree") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
treeItem.expand();
return Status.OK_STATUS;
}
};
expandJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
final int maxAttempts = 30;
int currentAttempt = 0;
while (currentAttempt < maxAttempts && treeItem.getItems().length == 1
&& treeItem.getItems()[0].getText().isEmpty()) {
SWTUtils.wait(1, TimeUnit.SECONDS);
currentAttempt++;
}
}
});
expandJob.schedule();
SWTUtils.wait(1, TimeUnit.SECONDS);
return treeItem;
}
public static SWTBotView getSWTBotView(final SWTWorkbenchBot bot, final String viewId) {
return bot.views().stream().filter(v -> v.getViewReference().getId().equals(viewId)).findFirst().orElse(null);
}
@SuppressWarnings("unchecked")
public static <T> T getView(final SWTWorkbenchBot bot, final String viewId) {
return (T) getView(bot, viewId, false);
}
@SuppressWarnings("unchecked")
public static <T> T getView(final SWTWorkbenchBot bot, final String viewId, final boolean restore) {
final SWTBotView viewBot = bot.viewById(viewId);
viewBot.setFocus();
return (T) viewBot.getReference().getView(restore);
}
/**
* @return <code>true</code> if the Console view is visible in the active
* page, <code>false</code> otherwise.
* @throws InterruptedException
*/
public static boolean isConsoleViewVisible(final SWTWorkbenchBot bot) {
return bot.views().stream()
.anyMatch(v -> v.getViewReference().getId().equals(IConsoleConstants.ID_CONSOLE_VIEW));
}
public static SWTBotToolbarButton getConsoleToolbarButtonWithTooltipText(final SWTWorkbenchBot bot, final String tooltipText) {
return bot.viewById(IConsoleConstants.ID_CONSOLE_VIEW).getToolbarButtons().stream()
.filter(button -> button.getToolTipText().equals(tooltipText)).findFirst().get();
}
public static void closeView(final SWTWorkbenchBot bot, final String viewId) {
bot.views().stream().filter(v -> v.getReference().getId().equals(viewId)).forEach(v -> v.close());
}
/**
* Creates a new {@link SWTBotMenu} from the context. This avoids some
* unexpected "Widget is disposed" errors.
*
* @param bot
* the bot
* @param menuName
* the name of the menu to find
* @return the context menu
* @see <a href=
* "https://www.eclipse.org/forums/index.php?t=msg&th=11863&start=0&">Eclipse
* forum</a>
*/
public static SWTBotMenu getContextMenu(final AbstractSWTBot<? extends Control> bot, final String menuName) {
return new SWTBotMenu(ContextMenuHelper.contextMenu(bot, menuName));
}
}